package cn.org.tpeach.nosql.tools;



import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.*;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * G 年代标志符 Era 标志符
 * y 年 Year
 * M 月 Month
 * d 日
 * h 时 (12小时制)
 * H 时 (24小时制)
 * m 分
 * s 秒
 * S 毫秒
 * E 星期几
 * D 一年中的第几天
 * F 一月中第几个星期(以每个月1号为第一周,8号为第二周为标准计算)
 * w 一年中第几个星期
 * W 一月中第几个星期(不同于F的计算标准,是以星期为标准计算星期数,例如1号是星期三,是当月的第一周,那么5号为星期日就已经是当月的第二周了)
 * a 上午 / 下午 标记符
 * k 时 (24小时制,其值与H的不同点在于,当数值小于10时,前面不会有0)
 * K 时 (12小时值,其值与h的不同点在于,当数值小于10时,前面不会有0)
 * 需要使用ZonedDateTime
 * z 时区 General time zone   Pacific Standard Time; PST; GMT-08:00
 * Z   时区    RFC 822 time zone   -0800
 */
public class DateTools {

    /**
     * 例如:2020-05-01
     */
    public static final String DATE = "yyyy-MM-dd";
    /**
     * 例如:2020-05-01 10:00:00
     */
    public static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss";
    /**
     * 例如:2020-05-01 10:00:00.003
     */
    public static final String DATE_TIMES = "yyyy-MM-dd HH:mm:ss.SSS";
    /**
     * 例如:10:00:00
     */
    public static final String TIME = "HH:mm:ss";
    /**
     * 例如:10:00
     */
    public static final String TIME_WITHOUT_SECOND = "HH:mm";

    /**
     * 例如:2020-05-01 10:00
     */
    public static final String DATE_TIME_WITHOUT_SECONDS = "yyyy-MM-dd HH:mm";

    private static final ThreadLocal<SimpleDateFormat> simplateDateFormatThread = ThreadLocal.withInitial(() -> new SimpleDateFormat(DATE_TIME));

    /**
     * 使用SimpleDateFormat 格式为： yyyy-MM-dd HH:mm:ss
     * @param dateTime
     * @return
     */
    public static Date parse2Date(String dateTime) throws ParseException {
        return simplateDateFormatThread.get().parse(dateTime);
    }


    //--------------------------------------------------------转换------------------------------------------------------------------

    public static LocalDate asLocalDate(Date date) {
        if(date == null){
            return null;
        }
        return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
    }

    public static LocalDateTime asLocalDateTime(long mills){
        return Instant.ofEpochMilli(mills).atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    public static LocalDateTime asLocalDateTime(Date date){
        if(date == null){
            return null;
        }
//        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

    }
    public static Date asDate(LocalDate localDate) {
        if(localDate == null){
            return null;
        }
        return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

    public static Date asDate(LocalDateTime dateTime){
        if(dateTime == null){
            return null;
        }
        return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    }
    public static Date asDate(long mills) {
        Date date=new Date();
        date.setTime(mills);
        return date;
    }

    /**
     *
     * @param dateTime
     * @return
     */
    public static Date parse2Date(String dateTime,String pattern) throws DateTimeParseException{
        return asDate(parse(dateTime,pattern));
    }

    /**
     * Clock.fixed(Instant.parse("2019-02-05T16:45:42.01Z"), ZoneId.of("Australia/Sydney"))
     * @param dateTime
     * @param pattern
     * @return
     * @throws DateTimeParseException
     */
    public static LocalDateTime parse(String dateTime,String pattern) throws DateTimeParseException{
        if(StringUtils.isBlank(dateTime) || StringUtils.isBlank(pattern) ){
            throw new IllegalArgumentException("dateTime或者pattern不能为空");
        }
//        return LocalDateTime.parse(dateTime,DateTimeFormatter.ofPattern(pattern));
        DateTimeFormatter fmt = new DateTimeFormatterBuilder().appendPattern(pattern)
//                                    .parseDefaulting(ChronoField.YEAR, 1970)
                                    .parseDefaulting(ChronoField.MONTH_OF_YEAR,1)
                                    .parseDefaulting(ChronoField.DAY_OF_MONTH,1)
                                    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                                    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                                    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                                    .toFormatter();
        //修复默认1970 http://www.voidcn.com/article/p-zukwwsmw-bwe.html
         TemporalAccessor temporalAccessor = fmt.parse(dateTime);
        // check year and year of era
        if (!temporalAccessor.isSupported(ChronoField.YEAR) && !temporalAccessor.isSupported(ChronoField.YEAR_OF_ERA)) {
            // parsed value doesn't have any year field
            // return another TemporalAccessor with default value for year
            // using year 1970 - change it to Year.now().getValue() for current year
            TemporalAccessor fianlAccessor = temporalAccessor;
            temporalAccessor =  new TemporalAccessor() {
                private int year = 1970;
                @Override
                public boolean isSupported(TemporalField field) {
                    // epoch day is used by LocalDate.from
                    if (field == ChronoField.YEAR_OF_ERA || field == ChronoField.EPOCH_DAY) {
                        return true;
                    } else {
                        return fianlAccessor.isSupported(field);
                    }
                }
                @Override
                public long getLong(TemporalField field) {
                    if (field == ChronoField.YEAR_OF_ERA) {
                        return year;
                        // epoch day is used by LocalDate.from
                    } else if (field == ChronoField.EPOCH_DAY) {
                        // Assuming the input always have month and day
                        // If that's not the case, you can change the code to use default values as well,
                        // and use MonthDay.of(month, day)
                        return MonthDay.from(fianlAccessor).atYear(year).toEpochDay();
                    } else {
                        return fianlAccessor.getLong(field);
                    }
                }
            };
        }
        return LocalDateTime.from(temporalAccessor);

    }

    /**
     *  默认格式yyyy-MM-dd HH:mm:ss
     * @param dateTime
     * @return
     * @throws DateTimeParseException
     */
    public static LocalDateTime parse(String dateTime) throws DateTimeParseException {
        return parse(dateTime,DATE_TIME);
    }

    private static String formatTemporal(Temporal temporal,String pattern){
        if(temporal == null){
            return null;
        }
        if(StringUtils.isBlank(pattern)){
            return null;
        }
        if(temporal instanceof LocalDateTime){
            return ((LocalDateTime)temporal).format(DateTimeFormatter.ofPattern(pattern));
        } else if(temporal instanceof LocalDate){
            return ((LocalDateTime)temporal).format(DateTimeFormatter.ofPattern(pattern));
        }else if(temporal instanceof LocalTime){
            return ((LocalTime)temporal).format(DateTimeFormatter.ofPattern(pattern));
        }else if(temporal instanceof ZonedDateTime){
            return ((ZonedDateTime)temporal).format(DateTimeFormatter.ofPattern(pattern));
        }
        throw new UnsupportedTemporalTypeException("不支持的时间格式"+temporal.getClass().getName());

    }
    /**
     *  格式化成字符串
     * @param dateTime
     * @param pattern
     * @return
     */
    public static String format(LocalDateTime dateTime,String pattern){
        return formatTemporal(dateTime,pattern);
    }
    /**
     * 格式化成字符串  默认格式yyyy-MM-dd HH:mm:ss
     * @param dateTime
     * @return
     */
    public static String format(LocalDateTime dateTime ){
        return format(dateTime,DATE_TIME);
    }
    /**
     *  格式化成字符串
     * @param dateTime
     * @param pattern
     * @return
     */
    public static String format(LocalTime dateTime,String pattern){
        return formatTemporal(dateTime,pattern);
    }
    /**
     *  格式化成字符串
     * @param dateTime
     * @param pattern
     * @return
     */
    public static String format(LocalDate dateTime,String pattern){
        return formatTemporal(dateTime,pattern);
    }
    /**
     *  格式化成字符串
     * @param dateTime
     * @param pattern
     * @return
     */
    public static String format(ZonedDateTime dateTime,String pattern){
        return formatTemporal(dateTime,pattern);
    }


    /**
     * 格式化成字符串  默认格式yyyy-MM-dd HH:mm:ss
     * @param date
     * @return
     */
    public static String format(Date date){
        return format(date,DATE_TIME);
    }

    /**
     * 格式化成字符串
     * @param date
     * @param pattern
     * @return
     */
    public static String format(Date date,String pattern){
        if(date == null){
            return null;
        }
        return  format(asLocalDateTime(date),pattern);
    }


    //----------------------------------------------------------获取------------------------------------------------------------------


    /**
     * 获取当前日期字符串
     * @param pattern
     * @return
     */
    public static String getCurrentDate(String pattern){

        if(StringUtils.contains(pattern,'z') || StringUtils.contains(pattern,'Z') ){
            return format(ZonedDateTime.now(),pattern);
        }
        return format(LocalDateTime.now(),pattern);
    }
    /**
     * 获取当前日期字符串
     * yyyy-MM-dd HH:mm:ss
     * @return
     */
    public static String getCurrentDate(){
        return format(LocalDateTime.now(),DATE_TIME);
    }
    /**
     * 获取当年
     *
     * @return 年
     */
    public static int getYear() {
        return LocalDate.now().getYear();
    }
    /**
     * 获取年
     *
     * @return 年
     */
    public static int getYear(LocalDateTime date) {
        return date.get(ChronoField.YEAR);
    }
    /**
     * 获取当月
     *
     * @return 月份
     */
    public static int getMonth() {
        return LocalDate.now().getMonth().getValue();
    }
    /**
     * 获取月份
     * @param date
     * @return 月份
     */

    public static int getMonth(LocalDateTime date) {
        return date.get(ChronoField.MONTH_OF_YEAR);
    }

    /**
     *  获取当月 返回字符串，自动补0
     * @return
     */
    public static String getMonthString(){
        return String.format("%02d",getMonth());
    }
    /**
     * 获取月份 返回字符串，自动补0
     * @param date
     * @return 月份
     */
    public static String getMonthString(LocalDateTime date){
        return String.format("%02d",getMonth(date));
    }

    /**
     * 获取当月的第几天
     *
     * @return 几号
     */
    public static int getDayOfMonth() {
        return LocalDate.now().getDayOfMonth();
    }
    /**
     * 获取某月的第几天
     *
     * @return 几号
     */
    public static int getDayOfMonth(LocalDateTime date) {
        return date.get(ChronoField.DAY_OF_MONTH);
    }

    /**
     * 获取当月的第几天 返回字符串，自动补0
     *
     * @return 几号
     */
    public static String getDayOfMonthString( ) {
        return String.format("%02d",getDayOfMonth());
    }
    /**
     * 获取某月的第几天 返回字符串，自动补0
     *
     * @return 几号
     */
    public static String getDayOfMonthString(LocalDateTime date) {
        return String.format("%02d",getDayOfMonth(date));
    }

    /**
     * 本月最后1天
     * @return
     */
    public static LocalDate lastDayOfMonth(){
        return LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
    }

    /**
     * 下月第1天
     * @return
     */
    public static LocalDate firstDayOfNextMonth(){
        return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
    }

    /**
     * 本月第1个周一
     * @return
     */
    public static LocalDate firstWeekday (){

        return LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
    }

    public static LocalDateTime getDateTime(int year,int month,int dayOfMonth,int hour,int minute,int second){
        return LocalDateTime.of(year, month, dayOfMonth, hour, minute, second);
    }

    public static long currentTimeSenconds(){
        return LocalDateTime.now().toEpochSecond(OffsetDateTime.now().getOffset());
    }

    public static long currentTimeMillis(){
//        return getTimeMillis(LocalDateTime.now());
//          return System.currentTimeMillis();
        return Clock.systemDefaultZone().millis();
    }

    /**
     * 获取指定日期的毫秒
     * @param time
     * @return
     */
    public static long getTimeMillis(LocalDateTime time) {
        return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    /**
     * 获取指定日期的秒
     * @param time
     * @return
     */
    public static long getSecondsByTime(LocalDateTime time) {
        return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
    }

    /**
     * 获取两个日期的差  field参数为ChronoUnit.*
     * @param startTime
     * @param endTime
     * @param field  单位(年月日时分秒)
     * @return
     */
    public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) {
        switch (field){
            case YEARS:
            case MONTHS:
                Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime));
                if (field == ChronoUnit.MONTHS){
                    return period.getYears() * 12 + period.getMonths();
                }
                return period.getYears();
            //提供了使用基于时间的值（如秒，纳秒）测量时间量的方法
            case SECONDS:
                return Duration.between(startTime,endTime).getSeconds();
            case MILLIS:
                return Duration.between(startTime,endTime).toMillis();
            case NANOS:
                return Duration.between(startTime,endTime).toNanos();
             default:
             // ChronoUnit类可用于在单个时间单位内测量一段时间，例如天数或秒
                return field.between(startTime, endTime);
        }
    }

    /**
     * 获取月末
     * @param dateTime
     * @return
     */
    public static LocalDateTime lastDayOfMonth(LocalDateTime dateTime){
        return dateTime.with(TemporalAdjusters.lastDayOfMonth());
    }
    /**
     * 获取月初
     * @param dateTime
     * @return
     */
    public static LocalDateTime firstDayOfMonth(LocalDateTime dateTime){
        return dateTime.with(TemporalAdjusters.firstDayOfMonth());
    }

    /**
     * 日期迭代器
     */
    private static class LocalDateIterator implements Iterator<LocalDateTime> {
        private LocalDateTime startTime;
        private LocalDateTime endTime;
        private ChronoUnit field;
        private int amount;
        private boolean isFinish = false;
        public LocalDateIterator(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field, int amount) {
            if(amount == 0){
                throw new IllegalArgumentException("amout不能为0");
            }
            this.startTime = startTime;
            this.endTime = endTime;
            this.field = field;
            this.amount = amount;
        }

        @Override
        public boolean hasNext() {
            if(startTime != null && endTime != null && !isFinish){
                //startTime 小于等于endTime
                if(amount > 0){
                    return !startTime.isAfter(endTime) ;
                }
                if(amount < 0){
                    //结束时间 小于等于 开始时间
                    return !endTime.isAfter(startTime) ;
                }
            }
            return false;
        }

        @Override
        public LocalDateTime next() {
            if(!hasNext()){
                throw new NoSuchElementException();
            }
            if(startTime.equals(endTime)){
                isFinish = true;
            }
            //获取的时间  开始时间往后相加
            LocalDateTime dateTime = startTime;
            startTime = startTime.plus(amount,field);
            if(amount > 0){
                if(startTime.isAfter(endTime)){
                    startTime = endTime;
                }
            }else if(amount < 0 ){
                if(startTime.isBefore(endTime)){
                    startTime = endTime;
                }
            }
            return dateTime;
        }
    }

    /**
     * 迭代日期 从startTime往后添加amount 直到 大于或等于endTime结束
     * @param startTime
     * @param endTime
     * @param field ChronoUnit
     * @param amount 正数或负数
     * @return
     */
    public static Iterator<LocalDateTime> iterator(LocalDateTime startTime,LocalDateTime endTime,ChronoUnit field,int amount){
        return  new LocalDateIterator(startTime,endTime,field,amount) ;
    }
    //-----------------------------------------------------------------判断-----------------------------------------------------------------
    public static boolean isSameInstant(final Date date1, final Date date2) {
        if (date1 == null || date2 == null) {
            return false;
        }
        return date1.getTime() == date2.getTime();
    }

    public static boolean isSameInstant(LocalDateTime date1,LocalDateTime date2) {
        if (date1 == null || date2 == null) {
            return false;
        }
        return getTimeMillis(date1) == getTimeMillis(date2);
    }

    public static boolean isSameDay(LocalDateTime date1,LocalDateTime date2){
        if(date1 == null || date2 == null){
            return false;
        }
        return date1.getDayOfYear() == date2.getDayOfYear()
                && date1.get(ChronoField.ERA) == date2.get(ChronoField.ERA)
                && date1.getYear() == date2.get(ChronoField.YEAR);
    }

    public static boolean isSameDay(Date date1,Date date2){
        return isSameDay(asLocalDateTime(date1),asLocalDateTime(date2));
    }
    public static boolean isSameDay(Calendar cal1,Calendar cal2){
        if(cal1 == null || cal2 == null){
            return false;
        }
        return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
                cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
    }

    public static boolean isSameMonth(LocalDateTime date1,LocalDateTime date2){
        if(date1 == null || date2 == null){
            return false;
        }
        return date1.getMonth() == date2.getMonth()
                && date1.get(ChronoField.ERA) == date2.get(ChronoField.ERA)
                && date1.getYear() == date2.get(ChronoField.YEAR);
    }

    public static boolean isSameMonth(Date date1,Date date2){
        return isSameMonth(asLocalDateTime(date1),asLocalDateTime(date2));
    }

    /**
     * 校验时期时间格式
     * @param date
     * @param pattern
     * @return
     */
    public static boolean isValidDate(String date,String pattern) {
        try{
            parse(date,pattern);
            return true;
        }catch (Exception e){
            return false;
        }
    }

    //-------------------------------------------------------操作时间--------------------------------------------------------------------
    /**
     * 为Date增加分钟,减传负数
     *
     * @param date        日期
     * @param plusMinutes 要增加的分钟数
     * @return 新的日期
     */
    public static Date addMinutes(Date date, Long plusMinutes) {
        if(date == null){
            return null;
        }
        LocalDateTime newDateTime = asLocalDateTime(date).plusMinutes(plusMinutes);
        return Date.from(newDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 增加时间
     *
     * @param date date
     * @param hour 要增加的小时数
     * @return new date
     */
    public static Date addHour(Date date, Long hour) {
        LocalDateTime dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        LocalDateTime localDateTime = dateTime.plusHours(hour);
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }


    /**
     * 将dateTime 时间当天起始时间 00:00:00
     * @param dateTime
     * @return
     */
    public static LocalDateTime firstTimeOfDay(LocalDateTime  dateTime){
            if(dateTime == null){
                return null;
            }
            LocalDateTime localDateTime = dateTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
            return localDateTime;
    }
    /**
     * 将dateTime 时间变成23:59:59
     * @param dateTime
     * @return
     */
    public static LocalDateTime lastTimeOfDay(LocalDateTime  dateTime){
        if(dateTime == null){
            return null;
        }
        LocalDateTime localDateTime = dateTime.withHour(23).withMinute(59).withSecond(59).withNano(999);
        return localDateTime;
    }

    /**
     *
     * @param localDate
     * @param firstDayOfWeek DayOfWeek.MONDAY ...
     * @param minimalDaysInFirstWeek the minimal number of days in the first week, from 1 to 7
     * @return
     */
    public static int getDayOfWeek(LocalDate localDate,DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek){
        WeekFields weekFields = WeekFields.of(firstDayOfWeek, minimalDaysInFirstWeek);
        return localDate.get(weekFields.dayOfWeek());
    }
    /**
     *
     * @param firstDayOfWeek DayOfWeek.MONDAY ...
     * @param minimalDaysInFirstWeek
     */
    public static int getDayOfWeek(DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek){
        return getDayOfWeek(LocalDate.now(),firstDayOfWeek,minimalDaysInFirstWeek);
    }

    public static String getDatePoor(long millisDiff) {
        long nd = 1000 * 24 * 60 * 60;
        long nh = 1000 * 60 * 60;
        long nm = 1000 * 60;
        long ns = 1000;
        // 获得两个时间的毫秒时间差异

        // 计算差多少天
        long day = millisDiff / nd;
        // 计算差多少小时
        long hour = millisDiff % nd / nh;
        // 计算差多少分钟
        long min = millisDiff % nd % nh / nm;
        // 计算差多少秒//输出结果
        long sec = millisDiff % nd % nh % nm / ns;
        return day + " day " + hour + " hour " + min + " min "+sec +" sec";
    }

    public static int getD(LocalDateTime dateTime,DayOfWeek firstDayOfWeek, int minimalDaysInFirstWeek){
        return getDayOfWeek(dateTime.toLocalDate(),firstDayOfWeek,minimalDaysInFirstWeek);
    }


    public static void main(String[] args) {
        String pattern = "yyyy-MM-dd HH:mm:ss Z z E MMM dd, yyyy";


        LocalDate date = LocalDate.of(2020,1,1);

        System.out.println(date.getDayOfWeek());
        System.out.println(getDayOfWeek(date,DayOfWeek.SUNDAY,7));
        System.out.println(DayOfWeek.MONDAY);

    }
}
